package com.oxygenecore.processor.description.impl;

import com.oxygenecore.processor.Expression;
import com.oxygenecore.processor.Statement;
import com.oxygenecore.processor.description.*;

import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;

/**
 * @author Askar Kalykov
 *         Date: 25.09.2010
 *         Time: 21:09:49
 */
public class ClassScannerXMLOutput implements ClassScannerListener {
    private PrintStream printStream;
    private boolean addXmlHeader;

    public ClassScannerXMLOutput(OutputStream outputStream, boolean addXmlHeader){
        if (outputStream == null){
            throw new NullPointerException("outputStream");
        }
        try {
            printStream = new PrintStream(outputStream, false, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        this.addXmlHeader = addXmlHeader;
    }


    private void printHeader(){
        if (addXmlHeader){
            printStream.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        }
    }

    private String escapeXml(String input){
        return input.replace("&","&amp;").replace("<","&lt;").replace(">","&gt;").replace("\"", "&quot;");
    }

    private void doTag(String tag, boolean composite, String ... attributes){
        if (attributes.length %2 == 1){
            throw new IllegalArgumentException("attribute length can not be even");
        }
        printStream.print(sb + "<" + tag + "");
        for (int i = 0; i < attributes.length; i+=2){
            printStream.print(" " + attributes[i] + "=\"" + escapeXml(attributes[i+1]) + "\"");
        }
        if (composite){
            printStream.println(">");
            fold();
        } else {
            printStream.println("/>");
        }
    }

    private void endTag(String tag){
        unfold();
        printStream.println(sb + "<" + tag + ">");
    }

    private StringBuilder sb = new StringBuilder();

    private static final String shift = " ";

    private void fold(){
        sb.append(shift);
    }

    private void unfold(){
        sb.setLength(sb.length()-shift.length());
    }

    public void beginTraverse() {
        printHeader();
        doTag("calculation", true);
    }

    public void endTraverse() {
        endTag("calculation");
    }

    public <C extends Expression<T>, T> void beginExpression(ExpressionClassInfo<C, T> expressionClassInfo) {
        doTag("expression", true, "class", expressionClassInfo.getTargetClass().getName(), "type", expressionClassInfo.getType().getName(), "leaf", "" + expressionClassInfo.isLeaf());
    }

    public void endExpression() {
        endTag("expression");
    }

    public <T extends Statement> void beginStatement(StatementClassInfo<T> statementClassInfo) {
        doTag("statement", true, "class", statementClassInfo.getTargetClass().getName());
    }

    public void endStatement() {
        endTag("statement");
    }

    public void beginConstructor(ConstructorInfo constructorInfo) {
        doTag("constructor", true);
    }

    public void endConstructor() {
        endTag("constructor");
    }

    public <T> void beginExpressionParameter(ExpressionParameterInfo<T> expressionParameterInfo) {
        doTag("expression", false, "type", expressionParameterInfo.getExpressionType().getName(), "leaf", "" + expressionParameterInfo.isLeaf());
    }

    public void endExpressionParameter() { }

    public void beginStatementParameter(StatementParameterInfo statementParameterInfo) {
        doTag("statement", false);
    }

    public void endStatementParameter() { }

    public <T extends ParameterInfo<I>, I> void beginListParameterInfo(ListParameterInfo<T, I> listParameterInfo) {
        T parameterInfo = listParameterInfo.getItemParameterInfo();
        if (parameterInfo instanceof ExpressionParameterInfo){
            ExpressionClassInfo info = (ExpressionClassInfo) parameterInfo;
            doTag("list", false, "type", "expression", "expression-type", info.getType().getName(), "expression-leaf", "" + info.isLeaf());
        } else if (parameterInfo instanceof StatementParameterInfo){
            doTag("list", false, "type", "statement");
        }
    }

    public void endListParameterInfo() {
    }
}
